package com.example.wkable.exoplayer.meida_codec

import android.Manifest
import android.content.pm.PackageManager
import android.media.*
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.SystemClock
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.view.Surface
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.WindowManager
import android.widget.ProgressBar
import android.widget.Toast
import com.example.wkable.exoplayer.R
import com.example.wkable.exoplayer.extensions.checkPermission
import com.example.wkable.exoplayer.extensions.toast
import java.nio.ByteBuffer
import java.util.concurrent.Executors

/**
 * todo
 * 1. 占位图
 * 2. pause->resume继续播放
 */
class MediaCodecActivity : AppCompatActivity(), SurfaceHolder.Callback {

    private val surfaceView by lazy {
        findViewById<SurfaceView>(R.id.surface)
    }
    private val progressBar by lazy {
        findViewById<ProgressBar>(R.id.progress)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        setContentView(R.layout.activity_media_codec)
        surfaceView.holder.addCallback(this)

//        testAudio()
        testKotlinFloat()
    }

    private fun testKotlinFloat() {
        val l1 = 1550828561257L
        val l2 = 1550828561272L
        val f1 = 40.0f
        val fl = l1 + f1 - l2
//        1.550828561227E12  1550828561227
        // 9223372036854775807L
//        1.55082857E12
//        1.55082857E12

        Log.e(TAG, "fl:$fl  Long:${1550828561297F}")
    }


    private fun testAudio() {
        threadPool.execute(AudioRender("${Environment.getExternalStorageDirectory()}/banhusha.mp3"))
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == 1) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//                playWithDirectCode()
                playWithVideoPlayer()
            } else {
                toast(this, "没有权限！", Toast.LENGTH_LONG)
            }
        }
    }

    private fun updateSurfaceDimension(ration: Float) {
        val widthPixels = resources.displayMetrics.widthPixels
        val height = widthPixels / ration
        val layoutParams = surfaceView.layoutParams
        layoutParams.height = height.toInt()
        surfaceView.layoutParams = layoutParams
    }

    class AudioRender(private val path: String) : Runnable {
        private var paused = false
        private var mediaExtractor: MediaExtractor? = null
        private var decoder: MediaCodec? = null
        private var audioTrack: AudioTrack? = null
        override fun run() {
            initCodec()
        }


        private fun initCodec() {
            val mediaExtractor = MediaExtractor()
            this.mediaExtractor = mediaExtractor
            try {
                mediaExtractor.setDataSource(path)
            } catch (e: Exception) {
                // ignored
            }

            val trackCount = mediaExtractor.trackCount
            var audioTrackIndex = -1
            for (i in 0..trackCount) {
                val trackFormat = mediaExtractor.getTrackFormat(i)
                val mime = trackFormat.getString(MediaFormat.KEY_MIME)
                Log.e(TAG, "mime:$mime")
                if (mime.startsWith("audio/")) {
                    audioTrackIndex = i
                    break
                }
            }
            if (audioTrackIndex == -1) {
                Log.e(TAG, "invalid audio track index:$audioTrackIndex")
                return
            }

            mediaExtractor.selectTrack(audioTrackIndex)

            // get audio format from MediaExtractor using AudioFormat
            val trackFormat = mediaExtractor.getTrackFormat(audioTrackIndex)
            val sampleRate = trackFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE)
            val channel = trackFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
            val duration = trackFormat.getLong(MediaFormat.KEY_DURATION)
            Log.e(TAG, "duration in ms:${duration / 1000}")
            val channelConfig = if (channel == 1) {
                AudioFormat.CHANNEL_OUT_MONO
            } else {
                AudioFormat.CHANNEL_OUT_STEREO
            }
            val audioFormat = AudioFormat.ENCODING_PCM_16BIT


            val minBufferSize = AudioTrack.getMinBufferSize(
                sampleRate, channelConfig,
                audioFormat
            )
            val bufferSize: Int =
                if (minBufferSize > 0) {
                    Log.e(TAG, "AUDIO->bufferSize->minBufferSize:$minBufferSize")
                    minBufferSize
                } else {
                    val maxBuffer = trackFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)
                    Log.e(TAG, "AUDIO->bufferSize->minBufferSize~:$maxBuffer")
                    maxBuffer
                }

            val audioTrack = AudioTrack(
                AudioManager.STREAM_MUSIC, sampleRate,
                channelConfig, audioFormat, bufferSize, AudioTrack.MODE_STREAM
            )
            this.audioTrack = audioTrack
            audioTrack.play()

            val mimeType = trackFormat.getString(MediaFormat.KEY_MIME)


            val decoder = MediaCodec.createDecoderByType(mimeType)
            this.decoder = decoder
            decoder.configure(trackFormat, null, null, 0)
            decoder.start()


            val bufferInfo = MediaCodec.BufferInfo()
            val startTimeInMs = System.currentTimeMillis()

            var byteArray: ByteArray? = null
            while (!paused) {
                val endOfFile = dequeueAndQueueInputBuffer(decoder, mediaExtractor)
                if (endOfFile) {
                    break
                }
                //// end of input stage.

                // Returns the index of an output buffer that has been successfully decoded or one of the INFO_* constants.
                val outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_US)
                Log.e(TAG, "bufferInfo:$bufferInfo")
                if (outputBufferIndex >= 0) {
                    /// 判断播放速度是否正确
                    while (System.currentTimeMillis() - startTimeInMs < bufferInfo.presentationTimeUs / 1000) {
                        SystemClock.sleep(20)
                    }

                    val buffer = if (Build.VERSION.SDK_INT >= 21) {
                        decoder.getOutputBuffer(outputBufferIndex)
                    } else {
                        decoder.outputBuffers[outputBufferIndex]
                    }
                    buffer ?: continue

                    if (Build.VERSION.SDK_INT >= 21) {
                        val write = audioTrack.write(buffer, bufferInfo.size, AudioTrack.WRITE_BLOCKING)
                        Log.e(TAG, "write cnt:$write size:${bufferInfo.size}")
                    } else {
                        val size = bufferInfo.size
                        if (byteArray == null || byteArray.size < size) {
                            byteArray = ByteArray(size)
                        }
                        buffer.get(byteArray, 0, bufferInfo.size)
                        val write = audioTrack.write(byteArray, 0, bufferInfo.size)
                        Log.e(TAG, "write cnt:$write size:${bufferInfo.size}")
                    }

                    decoder.releaseOutputBuffer(outputBufferIndex, true)
                } else {
                    Log.e(TAG, "dequeueOutputBuffer:$outputBufferIndex")
                }
            }
            release()
        }

        private fun dequeueAndQueueInputBuffer(decoder: MediaCodec, mediaExtractor: MediaExtractor): Boolean {
            // get buffer from MediaCodec
            val bufferIndex = decoder.dequeueInputBuffer(TIMEOUT_US)
            val buffer = if (Build.VERSION.SDK_INT >= 21) {
                decoder.getInputBuffer(bufferIndex)
            } else {
                decoder.inputBuffers[bufferIndex]
            }
            buffer ?: return true

            // fill buffer with data using MediaExtractor
            // the sample size (or -1 if no more samples are available).
            val readResult = mediaExtractor.readSampleData(buffer, 0)
            buffer.clear()
            // judge whether reaching end of the file.
            return if (readResult < 0) {
                decoder.queueInputBuffer(bufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
                Log.e(TAG, "END OF FILE, STOP PLAYING...")
                true
            } else {
                try {
                    decoder.queueInputBuffer(bufferIndex, 0, buffer.limit(), mediaExtractor.sampleTime, 0)
                    mediaExtractor.advance()
                    false
                } catch (e: java.lang.Exception) {
                    android.util.Log.e(TAG, "queueInputBuffer:$e")
                    true
                }

            }
        }

        fun release() {
            mediaExtractor?.release()
            audioTrack?.release()
            decoder?.stop()
            decoder?.release()
        }


        fun pause() {
            paused = true
        }
    }


    class VideoRender(private val path: String, private val surface: Surface) : Runnable {
        private var listener: VideoListener? = null
        private var paused = false
        override fun run() {
            initCodec()
        }

        private var mediaExtractor: MediaExtractor? = null
        private var decoder: MediaCodec? = null

        private fun initCodec() {
            val mediaExtractor = MediaExtractor()
            this.mediaExtractor = mediaExtractor
            try {
                mediaExtractor.setDataSource(path)
            } catch (e: Exception) {
                // ignored
            }

            val trackCount = mediaExtractor.trackCount
            var videoTrackIndex = -1
            for (i in 0..trackCount) {
                val trackFormat = mediaExtractor.getTrackFormat(i)
                val mime = trackFormat.getString(MediaFormat.KEY_MIME)
                Log.e(TAG, "mime:$mime")
                if (mime.startsWith("video/")) {
                    videoTrackIndex = i
                    break
                }
            }
            if (videoTrackIndex == -1) {
                Log.e(TAG, "invalid video track index:$videoTrackIndex")
                return
            }

            mediaExtractor.selectTrack(videoTrackIndex)


            val trackFormat = mediaExtractor.getTrackFormat(videoTrackIndex)
            val frameRate = trackFormat.getInteger(MediaFormat.KEY_FRAME_RATE)
            Log.e(TAG, "frameRate:$frameRate")
            val width = trackFormat.getInteger(MediaFormat.KEY_WIDTH)
            val height = trackFormat.getInteger(MediaFormat.KEY_HEIGHT)
            val duration = trackFormat.getLong(MediaFormat.KEY_DURATION)// in us
            listener?.onVideoParamsAvailable(VideoParams(width, height, duration / 1000))


            val mimeType = trackFormat.getString(MediaFormat.KEY_MIME)
            val decoder = MediaCodec.createDecoderByType(mimeType)
            this.decoder = decoder
            decoder.configure(trackFormat, surface, null, 0)
            decoder.start()

            val bufferInfo = MediaCodec.BufferInfo()
            val startTimeInMs = System.currentTimeMillis()
            while (!paused) {
                val eof = dequeueAndQueueInputBuffer(decoder, mediaExtractor)
                if (eof) {
                    break
                }
                //// end of input stage.

                // Returns the index of an output buffer that has been successfully decoded or one of the INFO_* constants.
                var outputBufferIndex: Int
                try {
                    outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_US)
                } catch (e: java.lang.Exception) {
                    break
                }

                if (outputBufferIndex >= 0) {
                    /// 判断播放速度是否正确
                    while (System.currentTimeMillis() - startTimeInMs < bufferInfo.presentationTimeUs / 1000) {
                        SystemClock.sleep(20)
                    }
                    listener?.onProgress((bufferInfo.presentationTimeUs / 1000).toInt())
                    decoder.releaseOutputBuffer(outputBufferIndex, true)
                } else {
                    Log.e(TAG, "dequeueOutputBuffer:$outputBufferIndex")
                    when (outputBufferIndex) {
                        MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {

                        }
                        MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED -> {

                        }
                        MediaCodec.INFO_TRY_AGAIN_LATER -> {

                        }

                    }
                }
            }
            release()
        }

        private fun getInputBuffers(bufferIndex: Int): ByteBuffer? {
            return if (Build.VERSION.SDK_INT >= 21) {
                decoder!!.getInputBuffer(bufferIndex)
            } else {
                decoder!!.inputBuffers[bufferIndex]
            }
        }

        private fun dequeueAndQueueInputBuffer(decoder: MediaCodec, mediaExtractor: MediaExtractor): Boolean {
            // get buffer from MediaCodec
            val bufferIndex = decoder.dequeueInputBuffer(TIMEOUT_US)
            if (bufferIndex < 0) {
                Log.e(TAG, "invalid input buffer index--->$bufferIndex")
                return false
            }
            val buffer = getInputBuffers(bufferIndex)
            buffer ?: return true

            // fill buffer with data using MediaExtractor
            // the sample size (or -1 if no more samples are available).
            val readResult = mediaExtractor.readSampleData(buffer, 0)
            buffer.clear()
            // judge whether reaching end of the file.
            return if (readResult < 0) {
                decoder.queueInputBuffer(bufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
                Log.e(TAG, "END OF FILE, STOP PLAYING...")
                true
            } else {
                try {
                    decoder.queueInputBuffer(bufferIndex, 0, buffer.limit(), mediaExtractor.sampleTime, 0)
                    mediaExtractor.advance()
                    false
                } catch (e: java.lang.Exception) {
                    Log.e(TAG, "queueInputBuffer:$e")
                    true
                }

            }
        }

        fun release() {
            mediaExtractor?.release()
            decoder?.stop()
            decoder?.release()
        }

        fun setListener(listener: VideoListener?) {
            this.listener = listener
        }

        fun pause() {
            paused = true
        }

        interface VideoListener {
            fun onVideoParamsAvailable(params: VideoParams)
            fun onProgress(timeInMs: Int)
        }

        data class VideoParams(var width: Int, var height: Int, var durationInMs: Long)
    }


    override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {

    }

    override fun surfaceDestroyed(holder: SurfaceHolder?) {
        videoRender?.pause()
        Log.e(TAG, "surfaceDestroyed")
    }

    override fun surfaceCreated(holder: SurfaceHolder?) {
        if (checkPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE, 1)) {
//            playWithDirectCode()
            playWithVideoPlayer()
        }
        Log.e(TAG, "surfaceCreated")
    }

    private fun playWithVideoPlayer() {
        object : Thread() {
            override fun run() {
                val videoPlayer = VideoPlayer(surfaceView.holder.surface)
                videoPlayer.setDataSource("${Environment.getExternalStorageDirectory()}/input.mp4")
                videoPlayer.start()
            }
        }.start()


    }

    private fun playWithDirectCode() {
        if (videoRender == null) {
            videoRender =
                VideoRender("${Environment.getExternalStorageDirectory()}/input.mp4", surfaceView.holder.surface)
            videoRender?.setListener(object : VideoRender.VideoListener {
                override fun onProgress(timeInMs: Int) {
                    runOnUiThread {
                        progressBar.progress = timeInMs
                        Log.e(TAG, "progress:$timeInMs")
                    }
                }

                override fun onVideoParamsAvailable(params: VideoRender.VideoParams) {
                    runOnUiThread {
                        progressBar.max = params.durationInMs.toInt()
                        progressBar.progress = 0
                        updateSurfaceDimension((params.width.toFloat() / params.height))
                        Log.e(TAG, "total:${params.durationInMs}")
                    }
                }
            })
            threadPool.execute(videoRender)
        }
    }

    private var videoRender: VideoRender? = null

    companion object {
        private const val TAG = "MediaCodecActivity"
        private const val TIMEOUT_US = 10 * 1000L
        private val threadPool = Executors.newFixedThreadPool(1)
    }
}
